# 知识点
# 1.生成器
# 2.迭代器


# 1.迭代器
# 迭代器是一个可以记住遍历的位置的对象
# 迭代器对象从集合的第一个元素开始访问，直到所有的元素被访问完结束。迭代器只能往前不会后退。
#
# 迭代器有两个基本的方法：iter() 和 next()。
list_for_iter = [1, 2, 3, 4]
it = iter(list_for_iter)  # 创建迭代器对象
print("iter test ", next(it))  # 输出迭代器的下一个元素


# 1.2创建一个迭代器
# 把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 。
# __iter__() 方法返回一个特殊的迭代器对象， 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
#
# __next__() 方法（Python 2 里是 next()）会返回下一个迭代器对象。
class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a <= 10:
            x = self.a
            self.a += 1
            return x
        else:
            # StopIteration 异常用于标识迭代的完成，防止出现无限循环的情况，在 __next__()
            # 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
            raise StopIteration


myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:
    print("iter test ", x)


# 2 生成器

# 2.1
# yield 表达式
# yield_expression ::=  "yield" [expression_list | "from" expression]
# yield 表达式在定义 generator 函数或是 asynchronous generator 的时候才会用到。 因此只能在函数定义的内部使用yield表达式。
# 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器，并且在一个 async def 定义的函数体内使用 yield 表达式会让协程函数变成异步的生成器
#
# 当一个生成器函数被调用的时候，它返回一个迭代器，称为生成器。然后这个生成器来控制生成器函数的执行。
# 执行到第一个yield表达式时，挂起，返回expression_list的值给调用者。
# 对于挂起，我们指的是所有局部状态被保留，包含当前局部变量的绑定，指令指针，内部调用栈，任何异常处理状态。
# 当通过调用生成器的一个函数恢复执行时，函数的执行就好像是从外部再次调用yield表达式一样。恢复执行后，yield表达式的值取决调用的方法。
# 如果是__next_()被调用（一般通过for循环或者内置的next（）函数），那么值是None。如果是send（）被调用，值是传给send的参数的值。

# 网友1说: yield 是什么东西呢，它相当于我们函数里的return。在每次next()，或者for遍历的时候，都会yield这里将新的值返回回去，并在这里阻塞，等待下一次的调用

# 2.2
# yield from 表达式
def accumulate():
    tally = 0
    while 1:
        print("1_1")
        var = yield
        if var is None:
            print("1_3")
            return tally
        print("1_2")
        tally += var


def gather_tallies(lists):
    while 1:
        print("1")
        tally = yield from accumulate()
        print("2")
        lists.append(tally)


#
test_list = []
iter5 = gather_tallies(test_list)
print("iter5 test", next(iter5))  # Ensure the accumulator is ready to accept values

for i in range(4):
    iter5.send(i)
iter5.send(None)
print("=test_list=", test_list)
#
for i in range(5):
    iter5.send(i)
iter5.send(None)  # Finish the second tally
print("=test_list=", test_list)


# 2.3生成器
# 在 Python 中，使用了 yield 的函数被称为生成器（generator）。
# 跟普通函数不同的是，生成器是一个返回迭代器的函数，只能用于迭代操作，更简单点理解生成器就是一个迭代器。
# 在调用生成器运行的过程中，每次遇到 yield 时函数会暂停并保存当前所有的运行信息，返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
# 即:generator函数，在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行。
#
# 调用一个生成器函数，返回的是一个迭代器对象。
def test_generator():
    print("generator test", 'before')
    yield  # break 1
    print("generator test", 'middle')
    yield  # break 2
    print("generator test", 'after')


# 调用一个生成器函数，返回的是一个迭代器对象
iter1 = test_generator()
next(iter1)  # 第一次调用 next(iter1)，程序执行到了 break 1 处就返回了
next(iter1)  # 第二次调用 next(iter1) 时从之前 yield 的位置（即 break 1） 处继续执行


# next(iter1)  # 第三次调用 next(iter1) 时从 break 2 恢复执行，最终退出函数时，抛出 StopIteration 异常，代表 generator 已经退出
# 因此我们看到 generator 的特性:可以从“顺序”执行流中暂时退出,并返回一些数据(返回 yield 右边的值)，之后能从暂停的位置恢复!!!
def test_generator2():
    num = 0
    sum = 0
    while True:
        sum += (yield sum / num if num > 0 else -10)  # 生成值:(sum / num if num > 0 else -10)
        num += 1
        # print("sum =", sum)
        # print("num =", num)


# 上面语法是这么工作的： yield 之前是语句，现在是表达式，是表达式就意味着我们能这么写 x = yield 10, y = 10 + (yield), foo(yield 42)。
# Python 规定，除非 yield 左边直接跟着等号（不准确），否则必须用扩号括起来。
# 当 Python 执行到 yield 表达式时，它首先计算 yield 右边的表达式，上例中即为 sum / num if num > 0 else 0 的值，暂停当前的控制流，并返回
iter2 = test_generator2()
# 先调用 iter2.send(None) 或 next(iter2) 来触发最开始的执行 ,(sum =0,num=0)
# 执行 yield 由不得表达式,返回默认值-10 ,然后方法挂起
print(iter2.send(None))
# 第二次调用  从当前位置继续运行,执行 yield 右边表达式赋值为 send()发送的值 1,(即改变了默认值-10),
# sum+=.., 执行之后就是 sum =1 num =1,接着 while 循环,执行 yield 右边表达式,返回 1 然后方法挂起
print(iter2.send(1))
# 第三次调用,继续从 yield 后执行,整个 yield 表达式被赋值为 5,此时sum =1 num =1,
# 执行sum += ..,num += 1 ,执行之后就是 sum =6 ,num=2,接着 while 循环,执行 yield 右边表达式,返回 3 然后方法挂起
print(iter2.send(5))
# 第四次调用,继续从 yield 后执行,整个 yield 表达式被赋值为 6,此时sum =6 num =2,
# 执行sum += ..,num += 1,执行之后就是 sum =12 ,num=3,接着 while 循环,执行 yield 右边表达式,返回 4 然后方法挂起
print(iter2.send(6))


# 如果使用 next(iter2)方法,方法恢复执行时，当前的 yield 表达式总是取值为 None


# 餐用升降机
# 当顾客点完菜,服务员把菜单放进餐用升降机，(send)送到厨房，然后在舱口等菜:
# R = kitchen.send("火腿煎蛋卷，配沙拉")
# 厨师等在升降机旁边,拿起菜单,准备菜,yields 给餐厅,等下一个订单.
# next_order = yield ["热乎乎的火腿煎蛋卷", "新鲜的沙拉"]
# 服务员拿到菜,端给顾客,然后拿回另一个顾客点的菜单.


#
#
#
#

#

#
# 11.8 异步IO
# 协程，又称微线程，纤程。英文名Coroutine。
# 子程序，或者称为函数，在所有语言中都是层级调用，比如A调用B，B在执行过程中又调用了C，C执行完毕返回，B执行完毕返回，最后是A执行完毕。
#
# 所以子程序调用是通过栈实现的，一个线程就是执行一个子程序。
#
# 子程序调用总是一个入口，一次返回，调用顺序是明确的。而协程的调用和子程序不同。
# 协程看上去也是子程序，但执行过程中，在子程序内部可中断，然后转而执行别的子程序，在适当的时候再返回来接着执行。
# 注意，在一个子程序中中断，去执行其他子程序，不是函数调用，有点类似CPU的中断。比如子程序A、B：
# def A():
#     print('1')
#     print('2')
#     print('3')
#
# def B():
#     print('x')
#     print('y')
#     print('z')
# 假设由协程执行，在执行A的过程中，可以随时中断，去执行B，B也可能在执行过程中中断再去执行A，结果可能是：
# 1
# 2
# x
# y
# 3
# z
# 但是在A中是没有调用B的，所以协程的调用比函数调用理解起来要难一些。
#
# 看起来A、B的执行有点像多线程，但协程的特点在于是一个线程执行，那和多线程比，协程有何优势？
#
# 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换，而是由程序自身控制，因此，没有线程切换的开销，和多线程比，线程数量越多，协程的性能优势就越明显。
#
# 第二大优势就是不需要多线程的锁机制，因为只有一个线程，也不存在同时写变量冲突，在协程中控制共享资源不加锁，只需要判断状态就好了，所以执行效率比多线程高很多。
#
# 因为协程是一个线程执行，那怎么利用多核CPU呢？最简单的方法是多进程+协程，既充分利用多核，又充分发挥协程的高效率，可获得极高的性能。
#
# Python对协程的支持是通过generator实现的。
# 在generator中，我们不但可以通过for循环来迭代，还可以不断调用next()函数获取由yield语句返回的下一个值。
#
# 但是Python的yield不但可以返回一个值，它还可以接收调用者发出的参数。
#
# 来看例子：
#
# 传统的生产者-消费者模型是一个线程写消息，一个线程取消息，通过锁机制控制队列和等待，但一不小心就可能死锁。
#
# 如果改用协程，生产者生产消息后，直接通过yield跳转到消费者开始执行，待消费者执行完毕后，切换回生产者继续生产，效率极高
def consumer():
    r = ''
    while True:
        # n = yield
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'


def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

# c = consumer()
# produce(c)

# 输出
# [PRODUCER] Producing 1...
# [CONSUMER] Consuming 1...
# [PRODUCER] Consumer return: 200 OK
# [PRODUCER] Producing 2...
# [CONSUMER] Consuming 2...
# [PRODUCER] Consumer return: 200 OK
# [PRODUCER] Producing 3...
# [CONSUMER] Consuming 3...
# [PRODUCER] Consumer return: 200 OK
# [PRODUCER] Producing 4...
# [CONSUMER] Consuming 4...
# [PRODUCER] Consumer return: 200 OK
# [PRODUCER] Producing 5...
# [CONSUMER] Consuming 5...
# [PRODUCER] Consumer return: 200 OK
# 注意到consumer函数是一个generator，把一个consumer传入produce后：
#
# 首先调用c.send(None)启动生成器；
#
# 然后，一旦生产了东西，通过c.send(n)切换到consumer执行；
#
# consumer通过yield拿到消息，处理，又通过yield把结果传回；
#
# produce拿到consumer处理的结果，继续生产下一条消息；
#
# produce决定不生产了，通过c.close()关闭consumer，整个过程结束。
#
# 整个流程无锁，由一个线程执行，produce和consumer协作完成任务，所以称为“协程”，而非线程的抢占式多任务。
#
# 最后套用Donald Knuth的一句话总结协程的特点：
#
# “子程序就是协程的一种特例。”


# asyncio
# asyncio是Python 3.4版本引入的标准库，直接内置了对异步IO的支持。
